#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <mutex>
#include "Util.hpp"
#include <boost/algorithm/string/case_conv.hpp>
#include <fstream>
#include "Log.hpp"

using namespace LogModule;
namespace ns_index
{
    struct DocInfo
    {
        std::string _title;
        std::string _content;
        std::string _url;
        uint64_t _id;
    };

    struct InvertedElem
    {
        uint64_t _id;      // 对应正排索引id
        int _weight;       // 权值
        std::string _word; // 关键字
    };

    class Index
    {
    private:
        Index()
        {
        }
        Index(const Index &) = delete;
        Index &operator=(const Index &) = delete;

    public:
        using InvertedList = std::vector<ns_index::InvertedElem>;
        static Index *GetInstance() // 单例，懒汉模式
        {
            if (_ins == nullptr)
            {
                _mutex.lock();
                if (_ins == nullptr)
                    _ins = new Index;
                _mutex.unlock();
            }
            return _ins;
        }

        // 根据doc_id找到找到文档内容
        DocInfo *GetForwardIndex(uint64_t doc_id)
        {
            if (doc_id >= _forwardIndex.size()) // 越界了
                return nullptr;
            return &_forwardIndex[doc_id];
        }

        // 根据关键字string，获得倒排拉链
        InvertedList *GetInvertedList(const std::string &word)
        {
            auto iter = _invertedIndex.find(word);
            if (iter == _invertedIndex.end())
                return nullptr;
            return &(iter->second);
        }

    private:
        // 建立正排索引
        DocInfo *BuildForwardIndex(const std::string &input)
        {
#define SEP "\3"
            DocInfo doc;
            std::vector<std::string> v;
            ns_util::StringUtil::CutString(&v, input, SEP);
            doc._title = v[0];
            doc._content = v[1];
            doc._url = v[2];

            doc._id = _forwardIndex.size();
            _forwardIndex.push_back(std::move(doc));

            return &_forwardIndex.back();
        }

        // 根据去标签，格式化之后的文档，构建正排和倒排索引
        void BuildIndex(const std::string &input) // parse处理完毕的数据交给我
        {
            // 1.解析input，构建正排索引
            DocInfo *doc = BuildForwardIndex(input);
            // 2.使用cppjieba，切分doc._title 和 doc._content
            std::vector<std::string> titlePart;
            std::vector<std::string> contentPart;
            ns_util::JieBaUtil::CutForSerach(doc->_title, &titlePart);
            ns_util::JieBaUtil::CutForSerach(doc->_content, &contentPart);
            // 3.分别对 标题title 和 正文content 进行词频统计
            struct Cnt
            {
                size_t _titleCnt = 0;
                size_t _wordCnt = 0;
            };
            std::unordered_map<std::string, Cnt> wordCount;
            for (std::string s : titlePart)
            {
                boost::algorithm::to_lower(s);
                wordCount[s]._titleCnt++; // size_t会调用默认构造初始化为0
            }
            for (std::string s : contentPart)
            {
                boost::algorithm::to_lower(s);
                wordCount[s]._wordCnt++;
            }
            // 4.计算权值，插入到 _invertedIndex
            // 标题分词 和 正文分词都需要添加到倒排索引中
            for (const auto &e : wordCount)
            {
                InvertedElem item;
                item._weight = e.second._titleCnt * 10 + e.second._wordCnt;
                item._id = doc->_id;
                item._word = e.first;
                _invertedIndex[e.first].push_back(std::move(item));
            }
        }

    public:
        // data/raw_html/raw.txt
        // 创建索引
        void InitIndex(const std::string &raw_path)
        {
            std::ifstream ifs(raw_path);
            if (!ifs.is_open())
                return;
            std::string input;
            int i = 0;
            while (getline(ifs, input))
            {
                BuildIndex(input);
                i++;
                if (i % 50 == 0)
                    LOG(LogLevel::INFO) << "当前建立索引:" << i;
                // std::cout << "当前建立索引:" << i << std::endl;
            }
            ifs.close();
        }

    public:
        static Index *_ins;
        static std::mutex _mutex;

    private:
        std::vector<DocInfo> _forwardIndex;                           // 正排索引，数组下标直接映射id
        std::unordered_map<std::string, InvertedList> _invertedIndex; // 倒排索引，关键字映射倒排索引结构
    };
    // 静态成员类内声明，类外初始化
    Index *Index::_ins = nullptr;
    std::mutex Index::_mutex;
}
